<!-- filepath: /Users/lixuecheng/workspace/jswork/tauri/t2/src/components/code.vue -->
<template>
    <div class="script-editor">
        <div class="toolbar">
            <a-space>
                <a-button @click="formatCode" class="f_row" style="gap: 4px">
                    <template #icon><format theme="outline" size="14" class="f" /></template>
                    格式化
                </a-button>
                <a-dropdown :trigger="['click']">
                    <a-button>
                        <div class="f_row" style="gap: 4px">
                            插入代码片段
                            <down theme="outline" class="f" size="16" />
                        </div>
                    </a-button>
                    <template #overlay>
                        <a-menu @click="insertSnippet">
                            <a-sub-menu key="vars" title="变量处理">
                                <a-menu-item key="getEnv">获取变量值</a-menu-item>
                                <a-menu-item key="setEnv">设置变量值</a-menu-item>
                                <a-menu-item key="getAllEnv">获取所有变量</a-menu-item>
                            </a-sub-menu>

                            <a-sub-menu key="time" title="时间处理">
                                <a-menu-item key="getCurrentTime">获取当前时间</a-menu-item>
                                <a-menu-item key="getTimestamp">获取时间戳</a-menu-item>
                                <a-menu-item key="addTimeNow">当前时间加减</a-menu-item>
                                <a-menu-item key="addTimeDate">指定时间加减</a-menu-item>
                            </a-sub-menu>

                            <a-sub-menu key="crypto" title="加密解密">
                                <a-sub-menu key="aes" title="AES加密">
                                    <a-menu-item key="aesEcbEncrypt">ECB加密</a-menu-item>
                                    <a-menu-item key="aesEcbDecrypt">ECB解密</a-menu-item>
                                    <a-menu-item key="aesCbcEncrypt">CBC加密</a-menu-item>
                                    <a-menu-item key="aesCbcDecrypt">CBC解密</a-menu-item>
                                </a-sub-menu>
                                <a-sub-menu key="hash" title="哈希函数">
                                    <a-menu-item key="md5">MD5</a-menu-item>
                                    <a-menu-item key="sha1">SHA1</a-menu-item>
                                    <a-menu-item key="sha256">SHA256</a-menu-item>
                                </a-sub-menu>
                                <a-sub-menu key="encoding" title="编码转换">
                                    <a-menu-item key="base64encode">Base64编码</a-menu-item>
                                    <a-menu-item key="base64decode">Base64解码</a-menu-item>
                                    <a-menu-item key="urlencode">URL编码</a-menu-item>
                                    <a-menu-item key="urldecode">URL解码</a-menu-item>
                                </a-sub-menu>
                            </a-sub-menu>

                            <a-sub-menu key="random" title="随机生成">
                                <a-sub-menu key="randomBasic" title="基础随机">
                                    <a-menu-item key="randomInt">随机整数</a-menu-item>
                                    <a-menu-item key="randomFloat">随机小数</a-menu-item>
                                    <a-menu-item key="randomString">随机字符串</a-menu-item>
                                </a-sub-menu>
                                <a-sub-menu key="randomAdvanced" title="高级随机">
                                    <a-menu-item key="uuid">生成UUID</a-menu-item>
                                    <a-menu-item key="randomPick">随机选择</a-menu-item>
                                </a-sub-menu>
                            </a-sub-menu>
                            <a-sub-menu key="request" title="请求处理">
                                <a-menu-item key="headers">请求头操作</a-menu-item>
                                <a-menu-item key="params">请求参数操作</a-menu-item>
                                <a-menu-item key="body">请求体操作</a-menu-item>
                            </a-sub-menu>
                        </a-menu>
                    </template>
                </a-dropdown>
                <a-button @click="runCodeModal()" class="f_row" style="gap: 4px">
                    <template #icon><play theme="outline" size="14" class="f" /></template>
                    调试
                </a-button>
                <slot></slot>
            </a-space>
        </div>
        <div :id="props.idd" class="editor-content"></div>
    </div>
    <!-- 调试弹窗 -->
    <a-modal
        v-model:open="debugModalVisible"
        title="代码调试"
        width="800px"
        :footer="null"
        :maskClosable="false"
        class="debug-modal"
    >
        <div class="debug-container">
            <div class="debug-config">
                <div class="config-item">
                    <div class="label">参数配置:</div>
                    <json-editor-vue
                        v-model="debugParamsObject"
                        :options="jsonEditorOptions"
                        class="json-editor-container"
                        @error="handleJsonError"
                    />
                </div>
                <div class="config-item">
                    <div class="label">超时时间(毫秒):</div>
                    <a-input-number v-model:value="debugTimeout" :min="1000" :max="300000" style="width: 200px" />
                </div>
                <a-button type="primary" @click="runDebug" :loading="debugLoading">
                    <template v-if="runningStatus.isRunning"> 运行中 ({{ runningStatus.elapsedTime }}) </template>
                    <template v-else> 运行调试 </template>
                </a-button>
            </div>

            <div v-if="debugResult" class="debug-result">
                <a-collapse>
                    <a-collapse-panel key="1" header="调试结果">
                        <div class="result-item">
                            <div class="label">执行时间:</div>
                            <div>{{ debugResult.executionTime }}ms</div>
                        </div>
                        <div class="result-item">
                            <div class="label">时间戳:</div>
                            <div>{{ debugResult.timestamp }}</div>
                        </div>
                        <div class="result-item">
                            <div class="label">最终参数:</div>
                            <pre>{{ JSON.stringify(debugResult.params, null, 2) }}</pre>
                        </div>
                        <div v-if="debugResult.error" class="result-item error">
                            <div class="label">错误信息:</div>
                            <pre>{{ JSON.stringify(debugResult.error, null, 2) }}</pre>
                        </div>
                        <div v-if="debugResult.consoleOutputs?.length" class="result-item">
                            <div class="label">控制台输出:</div>
                            <div class="console-outputs">
                                <div
                                    v-for="(output, index) in debugResult.consoleOutputs"
                                    :key="index"
                                    class="console-item"
                                >
                                    <span class="time">[{{ output.timestamp }}]</span>
                                    <span :class="output.type">{{ output.args.join(" ") }}</span>
                                </div>
                            </div>
                        </div>
                    </a-collapse-panel>
                </a-collapse>
            </div>
        </div>
    </a-modal>
</template>

<script setup>
import { ref, onMounted, watch } from "vue"
import { Down, Format, Play } from "@icon-park/vue-next"
import { createWorker } from "../utils/runScript"
import * as JSONEditor from "jsoneditor" //必须要的
import JsonEditorVue from "json-editor-vue3"
import {
    EditorView,
    keymap,
    highlightActiveLine,
    highlightSpecialChars,
    drawSelection,
    dropCursor,
    rectangularSelection,
    highlightActiveLineGutter,
    lineNumbers,
} from "@codemirror/view"
import { EditorState } from "@codemirror/state"
import {
    defaultHighlightStyle,
    syntaxHighlighting,
    indentOnInput,
    bracketMatching,
    foldGutter,
    foldKeymap,
} from "@codemirror/language"
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"
import { javascript } from "@codemirror/lang-javascript"
import { oneDark } from "@codemirror/theme-one-dark"
import { search } from "@codemirror/search"
import { autocompletion } from "@codemirror/autocomplete"
import { lintGutter } from "@codemirror/lint"
import { js_beautify } from "js-beautify"
import { message } from "ant-design-vue"
const editor = ref(null)

const props = defineProps({
    value: {
        type: String,
        default: "",
    },
    readonly: {
        type: Boolean,
        default: false,
    },
    idd: {
        type: String,
        default: "coder",
    },
})

watch(
    () => props.value,
    (value) => {
        if (!editor.value) return
        if (value == editor.value.state.doc.toString()) return
        editor.value.dispatch({
            changes: {
                from: 0,
                to: editor.value.state.doc.length,
                insert: value,
            },
        })
    },
)

const codeSnippets = {
    // 变量处理
    getEnv: `// 获取变量值
const value = getParam('variableName');\n`,
    setEnv: `// 设置变量值
setParam('variableName', 'value');\n`,
    getAllEnv: `// 获取所有变量
const allParams = getAllParams();\n`,

    // 时间处理
    getCurrentTime: `// 获取当前时间,可以指定格式
const now = time.format();\n`,
    getTimestamp: `// 获取时间戳(毫秒)
const timestamp = time.now();\n`,

    addTimeNow: `// 当前时间加减秒数
const future = time.addSeconds(3600); // 当前时间加1小时
const past = time.addSeconds(-1800); // 当前时间减30分钟
// 获取时间戳
const futureTs = time.addSecondsTimestamp(3600); // 当前时间加1小时的时间戳\n`,
    addTimeDate: `// 指定时间加减秒数
const date = '2024-01-20 10:00:00';
const future = time.addSecondsToDate(date, 3600); // 指定时间加1小时
const past = time.addSecondsToDate(date, -1800); // 指定时间减30分钟
// 获取时间戳
const futureTs = time.addSecondsToDateTimestamp(date, 3600); // 指定时间加1小时的时间戳\n`,

    // AES加密
    aesEcbEncrypt: `// AES-ECB加密
const encrypted = aes.encryptECB('明文', '密钥');\n`,
    aesEcbDecrypt: `// AES-ECB解密
const decrypted = aes.decryptECB(encrypted, '密钥');\n`,
    aesCbcEncrypt: `// AES-CBC加密
const encrypted = aes.encryptCBC('明文', '密钥', 'iv值');\n`,
    aesCbcDecrypt: `// AES-CBC解密
const decrypted = aes.decryptCBC(encrypted, '密钥', 'iv值');\n`,

    // 哈希函数
    md5: `// MD5哈希
const md5Value = hash.md5('text');\n`,
    sha1: `// SHA1哈希
const sha1Value = hash.sha1('text');\n`,
    sha256: `// SHA256哈希
const sha256Value = hash.sha256('text');\n`,

    // 编码转换
    base64encode: `// Base64编码
const encoded = base64.encode('text');\n`,
    base64decode: `// Base64解码
const decoded = base64.decode(encoded);\n`,
    urlencode: `// URL编码
const encoded = url.encode('text');\n`,
    urldecode: `// URL解码
const decoded = url.decode(encoded);\n`,

    // 随机生成
    randomInt: `// 生成1-100之间的随机整数
const num = random.int(1, 100);\n`,
    randomFloat: `// 生成0-1之间的随机小数，保留2位小数
const num = random.float(0, 1, 2);\n`,
    randomString: `// 生成10位随机字符串
const str = random.string(10);\n`,
    uuid: `// 生成UUID
const uuid = random.uuid();\n`,
    randomPick: `// 从数组中随机选择一个元素
const item = random.pick(['a', 'b', 'c']);\n`,

    // 请求处理
    headers: `request.headers['Custom-Header'] = 'value';\n`,
    params: `request.query['param'] = 'value';\n`,
    body: `request.body = 'xxx';\n`,
}
const emit = defineEmits(["update:value"])

// 调试相关的状态
const debugModalVisible = ref(false)
const debugParamsObject = ref({})
const debugTimeout = ref(60000)
const debugResult = ref(null)
const debugLoading = ref(false)

// JSON编辑器配置
const jsonEditorOptions = {
    mode: "code",
    modes: ["code", "view"],
    mainMenuBar: true,
    navigationBar: false,
    statusBar: false,
}
// 处理JSON编辑器错误
const handleJsonError = (error) => {
    if (error) {
        message.error("JSON格式错误：" + error.message)
    }
}

// 打开调试弹窗
const runCodeModal = () => {
    debugModalVisible.value = true
    debugParamsObject.value = {}
    debugTimeout.value = 60000
    debugResult.value = null
}
// 运行调试
// 运行状态
const runningStatus = ref({
    isRunning: false,
    startTime: null,
    elapsedTime: "0s",
})

// 更新运行时间
const updateElapsedTime = () => {
    if (runningStatus.value.startTime && runningStatus.value.isRunning) {
        const elapsed = (Date.now() - runningStatus.value.startTime) / 1000
        runningStatus.value.elapsedTime = `${elapsed.toFixed(1)}s`
        requestAnimationFrame(updateElapsedTime)
    }
}

const runDebug = async () => {
    try {
        debugLoading.value = true
        runningStatus.value.isRunning = true
        runningStatus.value.startTime = Date.now()
        runningStatus.value.elapsedTime = "0s"
        updateElapsedTime()

        const code = editor.value.state.doc.toString()
        if (!code.trim()) {
            throw new Error("代码不能为空")
        }

        console.log("开始执行代码:", code)
        console.log("参数:", debugParamsObject.value)
        const pp = {}
        for (const key in debugParamsObject.value) {
            pp[key] = debugParamsObject.value[key]
        }

        const result = await createWorker(code, pp, debugTimeout.value)
        console.log("执行结果:", result)

        debugResult.value = result
    } catch (error) {
        console.error("执行错误:", error)
        debugResult.value = error
        message.error(error.message || "执行出错")
    } finally {
        debugLoading.value = false
        runningStatus.value.isRunning = false
        runningStatus.value.startTime = null
    }
}
const formatCode = () => {
    if (props.readonly) {
        message.warn({
            content: "只读模式下无法编辑",
            key: "codekey",
        })
        return
    }
    if (!editor.value.state.doc.length) {
        return
    }
    if (editor.value.state.doc.length > 10000) {
        message.warn({
            content: "代码行数超过10000行，无法格式化",
            key: "codekey",
        })
        return
    }
    const code = editor.value.state.doc.toString()
    const formattedCode = js_beautify(code, {
        indent_size: 2,
        space_in_empty_paren: true,
    })
    editor.value.dispatch({
        changes: {
            from: 0,
            to: editor.value.state.doc.length,
            insert: formattedCode,
        },
    })
    message.success("代码格式化完成")
    emit("update:value", editor.value.state.doc.toString())
}

const insertSnippet = ({ key }) => {
    if (props.readonly) {
        message.warn({
            content: "只读模式下无法编辑",
            key: "codekey",
        })
        return
    }
    const snippet = codeSnippets[key]
    if (!snippet) return

    editor.value.dispatch({
        changes: {
            from: editor.value.state.selection.main.from,
            to: editor.value.state.selection.main.to,
            insert: snippet,
        },
    })
    emit("update:value", editor.value.state.doc.toString())
}

onMounted(() => {
    const state = EditorState.create({
        doc: "",
        extensions: [
            keymap.of(defaultKeymap),
            history(),
            javascript(),
            oneDark,
            EditorView.lineWrapping,
            EditorState.tabSize.of(4),
            // 显示行号
            lineNumbers(),
            // 高亮当前行
            highlightActiveLine(),
            // 高亮特殊字符
            highlightSpecialChars(),
            highlightActiveLineGutter(),
            // 高亮匹配的括号
            bracketMatching(),
            // 代码折叠
            foldGutter(),

            // 自动完成
            autocompletion(),

            // 代码检查
            lintGutter(),
            // 搜索支持

            search(),
            // 设置只读模式
            // EditorState.readOnly.of(props.readonly),
            // 编辑器主题配置
            EditorView.theme({
                "&": {
                    fontSize: "14px",
                    height: "100%",
                    width: "100%",
                },
                ".cm-scroller": {
                    fontFamily: "'Fira Code', monospace",
                    lineHeight: "1.6",
                },
                ".cm-gutters": {
                    backgroundColor: "var(--Neutral-11)",
                    color: "#676f7d",
                    border: "none",
                },
                ".cm-activeLineGutter": {
                    backgroundColor: "var(--Neutral-10)",
                },
                ".cm-foldGutter": {
                    color: "var(--Neutral-11)",
                },
                ".cm-lineNumbers": {
                    minWidth: "2em",
                },
            }),
        ],
    })

    editor.value = new EditorView({
        state,
        parent: document.getElementById(props.idd),
    })

    // 设置编辑器内容
    editor.value.dispatch({
        changes: {
            from: 0,
            to: editor.value.state.doc.length,
            insert: props.value,
        },
    })
    // 监听编辑器内容变化
    editor.value.dom.addEventListener("input", () => {
        if (!editor.value) {
            return
        }
        if (props.readonly) {
            message.warn({
                content: "只读模式下无法编辑",
                key: "codekey",
            })
            editor.value.dispatch({
                changes: {
                    from: 0,
                    to: editor.value.state.doc.length,
                    insert: props.value,
                },
            })

            return
        }
        const code = editor.value.state.doc.toString()
        emit("update:value", code)
    })
})
</script>

<style scoped>
.script-editor {
    height: 99%;
    width: 100%;
    display: flex;
    flex-direction: column;
    background-color: var(--Neutral-11);
}

.toolbar {
    padding: 8px;
    background-color: var(--Neutral-11);
    border-bottom: 1px solid #181a1f;
}

.editor-content {
    flex: 1;
    overflow: auto;
}
.debug-modal {
    :deep(.ant-modal-body) {
        padding: 16px;
        max-height: 80vh;
        overflow-y: auto;
    }
}

.debug-container {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.json-editor-container {
    height: 300px;
    border: 1px solid var(--Neutral-9);
    border-radius: 4px;
}

:deep(.jsoneditor) {
    border: none;
    background-color: var(--Neutral-11);
}

:deep(.jsoneditor-menu) {
    background-color: var(--Neutral-10);
    border-bottom: 1px solid var(--Neutral-9);
}

:deep(.jsoneditor-menu > button) {
    color: var(--character-85);
}

:deep(.ace-jsoneditor) {
    background-color: var(--Neutral-11);
    color: var(--character-85);
}

:deep(.ace-jsoneditor .ace_gutter) {
    background-color: var(--Neutral-10);
    color: var(--character-60);
}

:deep(.ace-jsoneditor .ace_marker-layer .ace_active-line) {
    background-color: var(--Neutral-10);
}

:deep(.ace-jsoneditor .ace_marker-layer .ace_selection) {
    background-color: var(--Neutral-9);
}

.debug-config {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.config-item {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.label {
    font-weight: 500;
    color: var(--character-85);
}

.debug-result {
    border-radius: 4px;
}

.result-item {
    margin-bottom: 16px;
}

.result-item pre {
    margin: 8px 0;
    padding: 12px;
    background-color: var(--Neutral-11);
    border-radius: 4px;
    overflow-x: auto;
}

.result-item.error pre {
    color: #ff4d4f;
}

.console-outputs {
    margin: 8px 0;
    padding: 12px;
    background-color: var(--Neutral-11);
    border-radius: 4px;
    max-height: 300px;
    overflow-y: auto;
}

.console-item {
    font-family: monospace;
    margin-bottom: 4px;
}

.console-item .time {
    color: var(--character-45);
    margin-right: 8px;
}

.console-item .log {
    color: var(--character-85);
}

.console-item .info {
    color: #1890ff;
}

.console-item .warn {
    color: #faad14;
}

.console-item .error {
    color: #ff4d4f;
}
</style>
<style>
/* 暗夜模式下 gutter 的样式 */
.cm-gutters {
    background-color: var(--Neutral-11); /* 暗夜模式背景色 */
    border-right: 1px solid #444; /* 分隔边框 */
    padding-right: 3px;
}

/* 行号样式 */
.cm-lineNumbers {
    color: #888; /* 行号颜色 */
}
/* 折叠标记样式 */
.cm-foldgutter-open,
.cm-foldgutter-close {
    color: #888; /* 折叠标记颜色 */
}

.cm-foldgutter-open::before,
.cm-foldgutter-close::before {
    content: "▼"; /* 折叠标记图标 */
    font-size: 14px;
}
/* 编辑器内容区域的背景色 */
.cm-content {
    background-color: var(--element-bg-color); /* 暗夜模式背景色 */
    color: #d4d4d4; /* 编辑器内容颜色 */
}
/* 当前行 gutter 高亮样式 */
.cm-activeLineGutter {
    background-color: var(--Neutral-11); /* 当前行 gutter 高亮背景色 */
}
.cm-activeLine {
    background-color: var(--Neutral-10) !important; /* 当前行高亮背景色 */
}
</style>
